/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// gl_warp.c -- sky and water polygons

#include "quakedef.h"


imagedata_t *Image_SetImage (imagedata_t *image, int width, int height, byte *data)
{
	image->width = width;
	image->height = height;
	image->data = data;

	return image;
}


#pragma pack (push, 1)
typedef struct targaheader_s
{
	unsigned char 	id_length, colormap_type, image_type;
	unsigned short	colormap_index, colormap_length;
	unsigned char	colormap_size;
	unsigned short	x_origin, y_origin, width, height;
	unsigned char	pixel_size, attributes;
} targaheader_t;

typedef struct pcx_s
{
	char	manufacturer;
	char	version;
	char	encoding;
	char	bits_per_pixel;
	unsigned short	xmin, ymin, xmax, ymax;
	unsigned short	hres, vres;
	unsigned char	palette[48];
	char	reserved;
	char	color_planes;
	unsigned short	bytes_per_line;
	unsigned short	palette_type;
	char	filler[58];
} pcx_t;
#pragma pack (pop)


/*
=============
Image_LoadTGA
=============
*/
imagedata_t *Image_LoadTGA (byte *data, imagedata_t *image)
{
	int				columns, rows, numPixels;
	byte			*pixbuf;
	int				row, column;
	byte			*targa_rgba;
	int				realrow; // fix for upside-down targas
	BOOL		upside_down; // fix for upside-down targas
	targaheader_t	*header = (targaheader_t *) data;

	if (header->image_type != 2 && header->image_type != 10) return NULL;
	if (header->colormap_type != 0 || (header->pixel_size != 32 && header->pixel_size != 24)) return NULL;

	columns = header->width;
	rows = header->height;
	numPixels = columns * rows;
	upside_down = !(header->attributes & 0x20); // fix for upside-down targas

	// jump forward to the actual data
	data += sizeof (targaheader_t);
	data += header->id_length;

	// uncompressed BGRA can just load directly
	if (header->image_type == 2 && header->pixel_size == 32 && !upside_down)
		return Image_SetImage (image, header->width, header->height, data);

	// otherwise take a chunk of memory and expand it
	targa_rgba = (byte *) qScratch->FastAlloc (numPixels * 4);

	if (header->image_type == 2)
	{
		// Uncompressed, RGB images
		for (row = rows - 1; row >= 0; row--)
		{
			// fix for upside-down targas
			realrow = upside_down ? row : rows - 1 - row;
			pixbuf = targa_rgba + realrow * columns * 4;

			for (column = 0; column < columns; column++)
			{
				*pixbuf++ = *data++;
				*pixbuf++ = *data++;
				*pixbuf++ = *data++;

				switch (header->pixel_size)
				{
				case 24: *pixbuf++ = 255; break;
				case 32: *pixbuf++ = *data++; break;
				}
			}
		}
	}
	else if (header->image_type == 10)
	{
		// Runlength encoded RGB images
		unsigned char packetHeader, packetSize, j;

		for (row = rows - 1; row >= 0; row--)
		{
			// fix for upside-down targas
			realrow = upside_down ? row : rows - 1 - row;
			pixbuf = targa_rgba + realrow * columns * 4;

			for (column = 0; column < columns;)
			{
				packetHeader = *data++;
				packetSize = 1 + (packetHeader & 0x7f);

				if (packetHeader & 0x80)
				{
					// run-length packet
					byte blue = *data++;
					byte green = *data++;
					byte red = *data++;
					byte alphabyte;

					switch (header->pixel_size)
					{
					case 24: alphabyte = 255; break;
					case 32: alphabyte = *data++; break;
					}

					for (j = 0; j < packetSize; j++)
					{
						*pixbuf++ = blue;
						*pixbuf++ = green;
						*pixbuf++ = red;
						*pixbuf++ = alphabyte;
						column++;

						if (column == columns)
						{
							// run spans across rows
							column = 0;

							if (row > 0)
								row--;
							else goto breakOut;

							// fix for upside-down targas
							realrow = upside_down ? row : rows - 1 - row;
							pixbuf = targa_rgba + realrow * columns * 4;
						}
					}
				}
				else
				{
					// non run-length packet
					for (j = 0; j < packetSize; j++)
					{
						*pixbuf++ = *data++;
						*pixbuf++ = *data++;
						*pixbuf++ = *data++;

						switch (header->pixel_size)
						{
						case 24: *pixbuf++ = 255; break;
						case 32: *pixbuf++ = *data++; break;
						}

						column++;

						if (column == columns)
						{
							// pixel packet run spans across rows
							column = 0;

							if (row > 0)
								row--;
							else goto breakOut;

							// fix for upside-down targas
							realrow = upside_down ? row : rows - 1 - row;
							pixbuf = targa_rgba + realrow * columns * 4;
						}
					}
				}
			}

breakOut:;
		}
	}

	return Image_SetImage (image, header->width, header->height, targa_rgba);
}


imagedata_t *Image_LoadD3D (byte *data, imagedata_t *image)
{
	/*
	IDirect3DTexture9 *tex = NULL;

	// load it as a system memory texture to perform format conversion/etc
	HRESULT hr = D3DXCreateTextureFromFileInMemoryEx (
		d3d_Device,
		data,
		com_filesize,
		D3DX_DEFAULT,
		D3DX_DEFAULT,
		1,
		0,
		D3DFMT_A8R8G8B8,
		D3DPOOL_SCRATCH,
		D3DX_DEFAULT,
		D3DX_DEFAULT,
		0,
		NULL,
		NULL,
		&tex
	);

	if (SUCCEEDED (hr))
	{
		D3DLOCKED_RECT lockrect;
		D3DSURFACE_DESC desc;

		if (SUCCEEDED (tex->GetLevelDesc (0, &desc)))
		{
			if (desc.Format == D3DFMT_A8R8G8B8)
			{
				if (SUCCEEDED (tex->LockRect (0, &lockrect, NULL, 0)))
				{
					image->data = (byte *) qScratch->FastAlloc (desc.Width * desc.Height * 4);

					D3D_CollapseRowPitch ((unsigned *) lockrect.pBits, desc.Width, desc.Height, lockrect.Pitch >> 2);
					memcpy (image->data, lockrect.pBits, desc.Width * desc.Height * 4);

					tex->UnlockRect (0);
					tex->Release ();

					return Image_SetImage (image, desc.Width, desc.Height, image->data);
				}
			}
		}
	}

	// something bad happened; release the texture if it was allocated and return NULL
	SAFE_RELEASE (tex);
	*/
	return NULL;
}


imagedata_t *Image_LoadPCX (byte *data, imagedata_t *image)
{
	pcx_t	*pcx = (pcx_t *) data;
	byte	*palette, *b, *image_rgba, *pbuf, *enddata;
	int		x, y, x2;
	int		width, height;

	if (com_filesize < sizeof (pcx_t) + 768) return NULL;
	if (pcx->manufacturer != 0x0a || pcx->version != 5 || pcx->encoding != 1 || pcx->bits_per_pixel != 8) return NULL;

	// base info
	width = pcx->xmax + 1;
	height = pcx->ymax + 1;
	palette = data + com_filesize - 768;
	data += sizeof (pcx_t);

	// no need to check for NULL cos we crash on a failed alloc instead
	image_rgba = (byte *) qScratch->FastAlloc (width * height * 4);

	// set up the image early so that we can modify pointers
	Image_SetImage (image, width, height, image_rgba);

	// decompress the palette indexes to the end of the image buffer
	pbuf = image_rgba + width * height * 3;
	enddata = palette;

	for (y = 0; y < height && data < enddata; y++)
	{
		byte *a = pbuf + y * width;

		for (x = 0; x < width && data < enddata;)
		{
			byte dataByte = *data++;

			if (dataByte >= 0xC0)
			{
				if (data >= enddata) break;

				x2 = x + (dataByte & 0x3F);
				dataByte = *data++;

				if (x2 > width) x2 = width;
				while (x < x2) a[x++] = dataByte;
			}
			else a[x++] = dataByte;
		}

		while (x < width) a[x++] = 0;
	}

	// expand to BGRA
	b = pbuf;

	for (x = 0; x < width * height; x++)
	{
		y = *b++ * 3;

		image_rgba[0] = palette[y + 2];
		image_rgba[1] = palette[y + 1];
		image_rgba[2] = palette[y + 0];
		image_rgba[3] = 255;

		image_rgba += 4;
	}

	return image;
}


imagedata_t *Image_LoadByName (char *name, imagedata_t *image, imagedata_t *(*LoadFunction) (byte *, imagedata_t *))
{
	int mark = qScratch->GetLowMark ();
	byte *data = (byte *) FS_LoadFile (name, qScratch);

	if (data)
		if ((LoadFunction (data, image)) != NULL)
			return image;

	// not opened at all
	qScratch->FreeToLowMark (mark);
	return NULL;
}


imagedata_t *Image_LoadImageFile (char *basename, imagedata_t *image)
{
	// try our different formats - D3DX can't load RLE TGAs so we use our own loader
	if ((Image_LoadByName (va ("%s.tga", basename), image, Image_LoadTGA)) != NULL) return image;

	// now try our D3DX formats in preferred order
	if ((Image_LoadByName (va ("%s.dds", basename), image, Image_LoadD3D)) != NULL) return image;
	if ((Image_LoadByName (va ("%s.png", basename), image, Image_LoadD3D)) != NULL) return image;
	if ((Image_LoadByName (va ("%s.bmp", basename), image, Image_LoadD3D)) != NULL) return image;
	if ((Image_LoadByName (va ("%s.jpg", basename), image, Image_LoadD3D)) != NULL) return image;

	// and finally try a PCX (D3DX can't load PCXs)
	if ((Image_LoadByName (va ("%s.pcx", basename), image, Image_LoadPCX)) != NULL) return image;

	// not found at all
	return NULL;
}


static float miptexel[256] = {
	0.000000, 0.000015, 0.000062, 0.000138, 0.000246, 0.000384, 0.000554, 0.000754, 0.000984, 0.001246, 0.001538, 0.001861, 0.002215, 0.002599, 0.003014, 0.003460,
	0.003937, 0.004444, 0.004983, 0.005552, 0.006151, 0.006782, 0.007443, 0.008135, 0.008858, 0.009612, 0.010396, 0.011211, 0.012057, 0.012933, 0.013841, 0.014779,
	0.015748, 0.016747, 0.017778, 0.018839, 0.019931, 0.021053, 0.022207, 0.023391, 0.024606, 0.025852, 0.027128, 0.028435, 0.029773, 0.031142, 0.032541, 0.033972,
	0.035433, 0.036924, 0.038447, 0.040000, 0.041584, 0.043199, 0.044844, 0.046521, 0.048228, 0.049965, 0.051734, 0.053533, 0.055363, 0.057224, 0.059116, 0.061038,
	0.062991, 0.064975, 0.066990, 0.069035, 0.071111, 0.073218, 0.075356, 0.077524, 0.079723, 0.081953, 0.084214, 0.086505, 0.088827, 0.091180, 0.093564, 0.095978,
	0.098424, 0.100900, 0.103406, 0.105944, 0.108512, 0.111111, 0.113741, 0.116401, 0.119093, 0.121815, 0.124567, 0.127351, 0.130165, 0.133010, 0.135886, 0.138793,
	0.141730, 0.144698, 0.147697, 0.150727, 0.153787, 0.156878, 0.160000, 0.163153, 0.166336, 0.169550, 0.172795, 0.176071, 0.179377, 0.182714, 0.186082, 0.189481,
	0.192910, 0.196371, 0.199862, 0.203383, 0.206936, 0.210519, 0.214133, 0.217778, 0.221453, 0.225160, 0.228897, 0.232664, 0.236463, 0.240292, 0.244152, 0.248043,
	0.251965, 0.255917, 0.259900, 0.263914, 0.267959, 0.272034, 0.276140, 0.280277, 0.284444, 0.288643, 0.292872, 0.297132, 0.301423, 0.305744, 0.310096, 0.314479,
	0.318893, 0.323337, 0.327812, 0.332318, 0.336855, 0.341423, 0.346021, 0.350650, 0.355310, 0.360000, 0.364721, 0.369473, 0.374256, 0.379070, 0.383914, 0.388789,
	0.393695, 0.398631, 0.403599, 0.408597, 0.413626, 0.418685, 0.423775, 0.428897, 0.434048, 0.439231, 0.444444, 0.449689, 0.454964, 0.460269, 0.465606, 0.470973,
	0.476371, 0.481799, 0.487259, 0.492749, 0.498270, 0.503822, 0.509404, 0.515017, 0.520661, 0.526336, 0.532042, 0.537778, 0.543545, 0.549343, 0.555171, 0.561030,
	0.566920, 0.572841, 0.578793, 0.584775, 0.590788, 0.596832, 0.602907, 0.609012, 0.615148, 0.621315, 0.627513, 0.633741, 0.640000, 0.646290, 0.652611, 0.658962,
	0.665344, 0.671757, 0.678201, 0.684675, 0.691180, 0.697716, 0.704283, 0.710880, 0.717509, 0.724168, 0.730857, 0.737578, 0.744329, 0.751111, 0.757924, 0.764767,
	0.771642, 0.778547, 0.785483, 0.792449, 0.799446, 0.806474, 0.813533, 0.820623, 0.827743, 0.834894, 0.842076, 0.849289, 0.856532, 0.863806, 0.871111, 0.878447,
	0.885813, 0.893210, 0.900638, 0.908097, 0.915586, 0.923106, 0.930657, 0.938239, 0.945852, 0.953495, 0.961169, 0.968874, 0.976609, 0.984375, 0.992172, 1.000000
};


int AverageMip (int _1, int _2, int _3, int _4)
{
	return (_1 + _2 + _3 + _4) >> 2;
}


int AverageMipGC (int _1, int _2, int _3, int _4)
{
	return (int) (255.0f * sqrt ((miptexel[_1] + miptexel[_2] + miptexel[_3] + miptexel[_4]) / 4.0f));
}


/*
================
GL_ResampleTexture
================
*/
unsigned *GL_ResampleTexture (unsigned *in, int inwidth, int inheight, int outwidth, int outheight)
{
	// these can happen on the last few miplevels
	if (outwidth < 1) outwidth = 1;
	if (outheight < 1) outheight = 1;

	// can this ever happen???
	if (outwidth == inwidth && outheight == inheight) return in;

	// allocating the out buffer before the pool mark so that we can return it
	unsigned *out = (unsigned *) qScratch->FastAlloc (outwidth * outheight * 4);

	// and now get the mark because all allocations after this will be released
	int mark = qScratch->GetLowMark ();

	unsigned *p1 = (unsigned *) qScratch->FastAlloc (outwidth * 4);
	unsigned *p2 = (unsigned *) qScratch->FastAlloc (outwidth * 4);

	unsigned fracstep = inwidth * 0x10000 / outwidth;
	unsigned frac = fracstep >> 2;

	for (int i = 0; i < outwidth; i++)
	{
		p1[i] = 4 * (frac >> 16);
		frac += fracstep;
	}

	frac = 3 * (fracstep >> 2);

	for (int i = 0; i < outwidth; i++)
	{
		p2[i] = 4 * (frac >> 16);
		frac += fracstep;
	}

	for (int i = 0; i < outheight; i++)
	{
		unsigned *outrow = out + (i * outwidth);
		unsigned *inrow0 = in + inwidth * (int) (((i + 0.25f) * inheight) / outheight);
		unsigned *inrow1 = in + inwidth * (int) (((i + 0.75f) * inheight) / outheight);

		for (int j = 0; j < outwidth; j++)
		{
			byte *pix1 = (byte *) inrow0 + p1[j];
			byte *pix2 = (byte *) inrow0 + p2[j];
			byte *pix3 = (byte *) inrow1 + p1[j];
			byte *pix4 = (byte *) inrow1 + p2[j];

			// don't gamma correct the alpha channel
			((byte *) &outrow[j])[0] = AverageMipGC (pix1[0], pix2[0], pix3[0], pix4[0]);
			((byte *) &outrow[j])[1] = AverageMipGC (pix1[1], pix2[1], pix3[1], pix4[1]);
			((byte *) &outrow[j])[2] = AverageMipGC (pix1[2], pix2[2], pix3[2], pix4[2]);
			((byte *) &outrow[j])[3] = AverageMip (pix1[3], pix2[3], pix3[3], pix4[3]);
		}
	}

	qScratch->FreeToLowMark (mark);

	return out;
}


/*
================
GL_MipMap

Operates in place, quartering the size of the texture
================
*/
unsigned *GL_MipMap (unsigned *data, int width, int height)
{
	width <<= 2;
	height >>= 1;

	// we can't mipmap in-place becase it causes artefacts with UploadSubresource so instead we alloc a new buffer.
	// i *think* it's because UploadSubresource is contention-free at creation-time so the original buffer was still in use
	// and shouldn't have been interfered with.  don't quote me on that one, though.  this is still useful to have because
	// we can reuse it for creating SRDs with immutable textures.  always a silver lining...
	unsigned *trans = (unsigned *) qScratch->FastAlloc ((width >> 1) * (height >> 1) * 4);
	byte *in = (byte *) data;
	byte *out = (byte *) trans;

	for (int i = 0; i < height; i++, in += width)
	{
		for (int j = 0; j < width; j += 8, out += 4, in += 8)
		{
			// don't gamma correct the alpha channel
			out[0] = AverageMipGC (in[0], in[4], in[width + 0], in[width + 4]);
			out[1] = AverageMipGC (in[1], in[5], in[width + 1], in[width + 5]);
			out[2] = AverageMipGC (in[2], in[6], in[width + 2], in[width + 6]);
			out[3] = AverageMip (in[3], in[7], in[width + 3], in[width + 7]);
		}
	}

	return trans;
}


unsigned *R_MipMap (unsigned *trans, int &Width, int &Height)
{
	// mip down in-place or resample down as appropriate
	if (((Width >> 1) << 1) != Width || ((Height >> 1) << 1) != Height)
		trans = GL_ResampleTexture (trans, Width, Height, Width >> 1, Height >> 1);
	else trans = GL_MipMap (trans, Width, Height);

	// take down the size
	if ((Width = Width >> 1) < 1) Width = 1;
	if ((Height = Height >> 1) < 1) Height = 1;

	return trans;
}


unsigned *R_ExpandTexture (byte *Data, int Width, int Height, int Flags)
{
	unsigned *trans = NULL;

	// expand it to 32-bit if needed
	if (Flags & TEX_32BIT)
		trans = (unsigned *) Data;
	else
	{
		unsigned *palette = (Flags & TEX_LUMA) ? d_8to24table_luma : d_8to24table_rgba;
		int s = Width * Height;

		trans = (unsigned *) qScratch->FastAlloc (s * 4);

		if (s & 3)
		{
			// can we not make this look prettier???
			for (int i = 0; i < s; i++)
				trans[i] = palette[Data[i]];
		}
		else
		{
			for (int i = 0; i < s; i += 4)
			{
				trans[i + 0] = palette[Data[i + 0]];
				trans[i + 1] = palette[Data[i + 1]];
				trans[i + 2] = palette[Data[i + 2]];
				trans[i + 3] = palette[Data[i + 3]];
			}
		}
	}

	// swap to BGRA if needed
	if (Flags & TEX_BGRA)
	{
		for (int i = 0; i < Width * Height; i++)
		{
			unsigned swap = trans[i];
			((byte *) &trans[i])[0] = ((byte *) &swap)[2];
			((byte *) &trans[i])[2] = ((byte *) &swap)[0];
		}
	}

	return trans;
}


void R_SetupSRD (D3D10_SUBRESOURCE_DATA *srd, unsigned *trans, int Width)
{
	srd->pSysMem = trans;
	srd->SysMemPitch = Width << 2;
	srd->SysMemSlicePitch = 0;
}


D3D10_SUBRESOURCE_DATA *R_CreateTextureSRDs (byte *Data, int Width, int Height, int ArraySize, int MipLevels, int Flags)
{
	// create a bunch of SRDs for the texture
	D3D10_SUBRESOURCE_DATA *srd = (D3D10_SUBRESOURCE_DATA *) qScratch->FastAlloc (MipLevels * ArraySize * sizeof (D3D10_SUBRESOURCE_DATA));

	// expand it now
	unsigned *trans = R_ExpandTexture (Data, Width, Height, Flags);

	// the first one just has the data
	R_SetupSRD (&srd[0], trans, Width);

	// if the texture isn't mipmapped then none of this loop will run
	for (int miplevel = 1; miplevel < MipLevels; miplevel++)
	{
		// generate this miplevel
		trans = R_MipMap (trans, Width, Height);

		// and set it up
		R_SetupSRD (&srd[miplevel], trans, Width);
	}

	return srd;
}


void D3D_CollapseRowPitch (unsigned *data, int width, int height, int pitch)
{
	if (width != pitch)
	{
		// as a minor optimization we can skip the first row
		// since out and data point to the same this is OK
		unsigned *out = data + width;
		data += pitch;

		for (int h = 1; h < height; h++)
		{
			for (int w = 0; w < width; w++)
				out[w] = data[w];

			out += width;
			data += pitch;
		}
	}
}


void D3D_Compress32To24WithSwapToBGR (byte *out, byte *in, int width, int height)
{
	for (int h = 0; h < height; h++)
	{
		for (int w = 0; w < width; w++, in += 4, out += 3)
		{
			out[2] = in[0];
			out[1] = in[1];
			out[0] = in[2];
		}
	}
}


void D3D_CollapseRowPitchWith32To24 (unsigned *data, int width, int height, int pitch)
{
	int h, w;
	byte *out = (byte *) data;

	for (h = 0; h < height; h++)
	{
		byte *in = (byte *) data;

		for (w = 0; w < width; w++, in += 4, out += 3)
		{
			out[0] = in[0];
			out[1] = in[1];
			out[2] = in[2];
		}

		data += pitch;
	}
}


void D3D_WriteDataToTGA (char *name, void *data, int width, int height, int bpp)
{
	if ((bpp == 24 || bpp == 8) && name && data && width > 0 && height > 0)
	{
		FILE *f = fopen (name, "wb");

		if (f)
		{
			byte header[18];

			memset (header, 0, 18);

			header[2] = 2;
			header[12] = width & 255;
			header[13] = width >> 8;
			header[14] = height & 255;
			header[15] = height >> 8;
			header[16] = bpp;
			header[17] = 0x20;

			fwrite (header, 18, 1, f);
			fwrite (data, (width * height * bpp) >> 3, 1, f);

			fclose (f);
		}
	}
}


void D3D_WriteDataToTGAWithPitchCollapseAnd32To24 (char *name, unsigned *data, int width, int height, int pitch)
{
	if (name && data && width > 0 && height > 0)
	{
		FILE *f = fopen (name, "wb");

		if (f)
		{
			byte header[18];
			int w, h;

			memset (header, 0, 18);

			header[2] = 2;
			header[12] = width & 255;
			header[13] = width >> 8;
			header[14] = height & 255;
			header[15] = height >> 8;
			header[16] = 24;
			header[17] = 0x20;

			fwrite (header, 18, 1, f);

			for (h = 0; h < height; h++)
			{
				for (w = 0; w < width; w++)
					fwrite (&data[w], 3, 1, f);

				data += pitch;
			}

			fclose (f);
		}
	}
}


